#include "Logger.h"
#include <time.h>
#include "common.h"

#ifdef _WIN32
#include <io.h>
#else
#include <sys/stat.h>
#include <sys/time.h>
#include <stdarg.h>
#endif // _WIN32

static uint64 GetFileLength(FILE* fp)
{
	if (!fp)
		return 0;

	fseek(fp, 0, SEEK_END);
	return ftell(fp);
}

Logger::Logger():
	m_fp(NULL),
	m_isInit(false),
	m_isLogConsole(false),
	m_curSize(0),
	m_level(0),
	m_fillFileNum(0),
	// m_line{ 0 },
	m_lastSec(0),
	m_thread(std::bind(&Logger::WriteFile, this), "logger"),
	m_working(false),
	m_writting(false),
	m_cond(m_mutex),
	//only logger thread
	m_formatTime{ 0 },
	m_lastCheckFile(0)
{
}


Logger::~Logger()
{
	m_working = false;
	{
		LockGuard lock(m_mutex);
		m_cond.CondBroadCast();
	}
	m_thread.Join();

	if (m_fp)
		fclose(m_fp);
}


bool Logger::Init(const std::string& path, const std::string& name, int logLevel, const std::string& codeHomePath, bool isLogConsole /*= true*/)
{
	if (name.length() == 0)
	{
		printf("init log file name is empty!\n");
		return false;
	}
	m_fileName = name;
	m_level = logLevel;
	m_codeHomePath = codeHomePath;
	m_isLogConsole = isLogConsole;
	
	size_t pos = path.find_last_of("/\\");
	if (path.length() == 0)
		m_filePath = "./";
	else if (pos == path.length() - 1)
		m_filePath = path;
	else if (pos == std::string::npos)
	{
		printf("init log path failed!\n");
		return false;
	}
	else
		m_filePath = path + "/";

	if (0 != access(m_filePath.c_str(), 0))			//0 == F_OK
	{
#ifdef _WIN32
		std::string command = "mkdir \"" + m_filePath + "\"";
		system(command.c_str());
#else
		std::string command = "mkdir -p " + path;
		system(command.c_str());
#endif // _WIN32

		if (0 != access(m_filePath.c_str(), 0))
		{
			printf("create log path failed!\n");
			return false;
		}
	}

	FormatTime(time(NULL));

	if (0 != m_thread.Start())
	{
		printf("init logger thread failed!\n");
		return false;
	}

	m_isInit = true;

	return true;
}


void Logger::WriteLog(int logLev, const char* file, int line, const char* function, const char* fmt, ...)
{
	if (!m_isInit)
		return;

	if (!fmt || LOG_LEVEL_TRACE > logLev || LOG_LEVEDL_MAX <= logLev)
		return;

	char timefmt[kFormatTimeLen]{ 0 };
	time_t now = time(NULL);
	{
		LockGuard lock(m_mutex);
		FormatTime(now);
		memcpy(timefmt, m_formatTime, kFormatTimeLen);
		if (!m_codeHomePath.empty())
		{
			std::string filePath(file);
			std::size_t pos = filePath.find(m_codeHomePath.c_str());
			if (pos != std::string::npos)
				file = &filePath[pos + m_codeHomePath.length()];
		}
	}

	// int n = snprintf(m_line + kFormatTimeLen, kMaxOneLine - kFormatTimeLen, "[%s](%s:%d)(%s)", s_logLevel2String[logLev], file, line, function);
	// int lineLen = kFormatTimeLen + n;
	// if (n < kMaxOneLine - kFormatTimeLen)
	// {
	// 	va_list ap;
	// 	va_start(ap, fmt);
	// 	int i = vsnprintf(m_line + kFormatTimeLen + n, kMaxOneLine - kFormatTimeLen - n, fmt, ap);
	// 	va_end(ap);
		
	// 	lineLen += i;
	// }

	char logline[kMaxOneLine]{ 0 };
	int n = snprintf(logline, kMaxOneLine, "[%s][%s](%s:%d)(%s)", timefmt, s_logLevel2String[logLev], file, line, function);
	if (n < kMaxOneLine)
	{
		va_list ap;
		va_start(ap, fmt);
		int i = vsnprintf(logline + n, kMaxOneLine - n, fmt, ap);
		va_end(ap);
		n += i;
	}

	//最后两位字符设为\n\0
	if (n >= kMaxOneLine - 2)
		n = kMaxOneLine - 2;
	logline[n] = '\n';
	logline[n + 1] = 0;

	{
		LockGuard lock(m_mutex);
		m_cacheq.push({ logline, logLev });
		if (!m_writting)
			m_cond.CondBroadCast();
	}
}


FILE* Logger::OpenLogFile()
{
	time_t now = time(NULL);
	tm nowTm;
	LocalTime(&nowTm, &now);
	
	char suffix[64]{ 0 };
	snprintf(suffix, sizeof(suffix), "-%4d%02d%02d.%d.log",
		nowTm.tm_year + 1900, nowTm.tm_mon + 1, nowTm.tm_mday, m_fillFileNum);
	
	m_curFilePath = m_filePath + m_fileName + suffix;
	
#ifdef _WIN32
	m_fp = _fsopen(m_curFilePath.c_str(), "ab+", _SH_DENYNO);
#else
	m_fp = fopen(m_curFilePath.c_str(), "ab+");
#endif // _WIN32

	if (!m_fp)
		return NULL;

	m_curSize = static_cast<int>(GetFileLength(m_fp));

	return m_fp;
}


int Logger::CloseLogFile()
{
	int ret = fclose(m_fp);
	m_fp = NULL;
	return ret;
}


void Logger::FlushLogFile()
{
	time_t now = time(NULL);
	
	if (now - m_lastCheckFile > kCheckFileExits && !CheckFileExits())
	{
		if (m_fp)
			CloseLogFile();

		if (!OpenLogFile())
			printf("check file exits open file failed!\n");
		return;
	}
	
	//第一次写文件时打开文件、隔日新建文件
	if (!m_fp || (now / DAY2SEC) != (m_lastCheckFile / DAY2SEC))
	{
		if(!OpenLogFile())
			printf("flush file open failed!\n");
		return;
	}

	//文件写满,新建一个文件
	if (kMaxFileSize <= m_curSize)
	{
		while (kMaxFileSize <= m_curSize)
		{
			++m_fillFileNum;
			CloseLogFile();
			if (!OpenLogFile())
				printf("file fill create new file filed!\n");
			return;
		}
	}
}


//检测文件是否存在
bool Logger::CheckFileExits()
{
	struct stat buffer;   
	return stat((m_filePath + m_fileName).c_str(), &buffer) == 0;
}


void Logger::FormatTime(time_t t)
{
	int msec = GetMSec();
	
	if (t != m_lastSec)
	{	
		tm nowTm;
		m_lastSec = t;
		LocalTime(&nowTm, &t);
		snprintf(m_formatTime, kFormatTimeLen, "%4d-%02d-%02d %02d:%02d:%02d.%03d",
		nowTm.tm_year + 1900, nowTm.tm_mon + 1, nowTm.tm_mday,
		nowTm.tm_hour, nowTm.tm_min, nowTm.tm_sec, msec);
	}
	else
	{
		snprintf(m_formatTime + 19, kFormatTimeLen - 19, ".%03d", msec);
	}
}


void Logger::WriteFile()
{
	std::queue<LogContext> logq;
	m_working = true;
	while (1)
	{
		{
			LockGuard lock(m_mutex);

			//logger销毁前,先把缓存中的日志消息写入文件.
			if (m_cacheq.empty())
			{
				if (!m_working)
				{
					printf("exit thread.\n");
					return;
				}
				m_cond.CondWait();
			}
			logq.swap(m_cacheq);
		}

		m_writting = true;
		while (!logq.empty())
		{
			FlushLogFile();
			
			LogContext& logContext = logq.front();
			std::string& logmsg = logContext.msg;
#ifdef _WIN32
			int i = _fwrite_nolock(logmsg.c_str(), 1, logmsg.length(), m_fp);
#else
			int i = fwrite_unlocked(logmsg.c_str(), 1, logmsg.length(), m_fp);
#endif	// _WIN32
			FlushFile();
			if (m_isLogConsole)
				printf("%s%s%s", s_ConsoleLogColor[logContext.level].head, logmsg.c_str(), s_ConsoleLogColor[logContext.level].tail);
			
			m_lastCheckFile = time(NULL);
			m_curSize += logmsg.length();
			logq.pop();
		}
		m_writting = false;
	}
}